#include <stm32f4xx.h>
#include <stm32f4xx_i2c.h>

#include "i2c_hal.h"

static void i2c_start(unsigned char address, enum X_I2C_MODE mode);
static void i2c_stop(unsigned address);
static unsigned char i2c_receive_byte_ack(void);
static unsigned char i2c_receive_byte_nack(void);
static unsigned char i2c_send_byte(const unsigned char ucdata);

static void i2c_stop(unsigned address)
{
    /* Send STOP Condition after last byte has been transmitted */
    I2C_GenerateSTOP(I2C_USED, ENABLE);
    /* wait for EV8_2 --> byte has been transmitted */
    while(!I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {}
}

unsigned char i2c_receive_byte_ack(void)
{
    /* Enable acknowledge of received data */
    I2C_AcknowledgeConfig(I2C_USED, ENABLE);
    /* Wait until one byte has been received */
    while( !I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_BYTE_RECEIVED) )
    {}
    /* Read data from I2C data register and return data byte */
    unsigned char data = (unsigned char) I2C_ReceiveData(I2C_USED);
    return data;
}

static unsigned char i2c_receive_byte_nack(void)
{
    /* Disable acknowledge of received data */
    I2C_AcknowledgeConfig(I2C_USED, DISABLE);
    I2C_GenerateSTOP(I2C_USED, ENABLE);
    /* Wait until one byte has been received */
    while( !I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_BYTE_RECEIVED) );
    /* Read data from I2C data register and return data byte */
    unsigned char data = (unsigned char) I2C_ReceiveData(I2C_USED);
    return data;
}

static unsigned char i2c_send_byte(const unsigned char ucdata)
{
    unsigned long timeout;
    I2C_AcknowledgeConfig(I2C_USED, ENABLE);
    /* Wait for I2C EV8 --> last byte is still being transmitted (last byte in SR, buffer empty), next byte can already be written */
    while(!I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_BYTE_TRANSMITTING))
    {}
    I2C_SendData(I2C_USED, ucdata);
    while(!I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {}
    return 1;
}

void i2c_open(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    I2C_InitTypeDef I2C_InitStruct;

    RCC_APB1PeriphClockCmd(I2C_CLK, ENABLE);
    RCC_AHB1PeriphClockCmd(I2C_GPIO_CLK, ENABLE);

    /*
     * setup SCL and SDA pins
     */
    GPIO_InitStruct.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStruct);

    GPIO_PinAFConfig(I2C_GPIO_PORT, I2C_SCL_SOURCE, I2C_AF_GPIO); /* SCL */
    GPIO_PinAFConfig(I2C_GPIO_PORT, I2C_SDA_SOURCE, I2C_AF_GPIO); /* SDA */

    /* configure I2C */
    I2C_InitStruct.I2C_ClockSpeed = I2C_CLOCK_HZ;
    I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStruct.I2C_OwnAddress1 = 0x00;
    I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; /* Enable ACK as default */
    I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_Init(I2C_USED, &I2C_InitStruct);

    I2C_Cmd(I2C_USED, ENABLE);
}

void i2c_close(void)
{
    I2C_DeInit(I2C_USED);
    GPIO_DeInit(I2C_GPIO_PORT);
}

static void i2c_start(unsigned char address, enum X_I2C_MODE mode)
{
    /* Wait until I2C is not busy any more */
    while (I2C_GetFlagStatus(I2C_USED, I2C_FLAG_BUSY))
    {}

    /* Send START condition */
    I2C_GenerateSTART(I2C_USED, ENABLE);

    /* Wait for I2C1 EV5 --> Slave has acknowledged start condition */
    while (!I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_MODE_SELECT))
    {}

    /* Send slave Address for write
     * Wait for I2C EV6, check if
     * either Slave has acknowledged Master transmitter or
     * Master receiver mode, depending on the transmission
     * direction
     */
    if (mode == READ) {
        I2C_Send7bitAddress(I2C_USED, address, I2C_Direction_Receiver);
        while(!I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
        {}
    } else if (mode == WRITE) {
        I2C_Send7bitAddress(I2C_USED, address, I2C_Direction_Transmitter);
        while(!I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
        {}
    } else {
        /* Call user error handler */
    }
}

unsigned long
i2c_read(unsigned char slave_address, unsigned char from_address, unsigned long length, unsigned char *pdata)
{
    unsigned long count = 0;
    i2c_start(slave_address << 1, WRITE);
    i2c_send_byte(from_address);
    i2c_stop(slave_address);
    if (!length) {
        return 0;
    } else if (length == 1) {
        i2c_start(slave_address << 1, READ);
        *pdata = i2c_receive_byte_nack();
        /* i2c_stop(slave_address); */
        count++;
    } else {
        i2c_start(slave_address << 1, READ);
        for (count = 1; count < length; count++) {
            *pdata++ = i2c_receive_byte_ack();
        }
        /* Last byte */
        *pdata = i2c_receive_byte_nack();
        /* i2c_stop(slave_address); */
    }
    return count;
}

unsigned long
i2c_write(unsigned char slave_address, unsigned char from_address, unsigned long length, unsigned char *pdata)
{
    unsigned long count = 0;
    i2c_start(slave_address << 1, WRITE);
    i2c_send_byte(from_address);
    if (!length) {
        return 0;
    } else {
        for (count = 0; count < length; count++) {
            i2c_send_byte(*pdata++);
        }
    }
    i2c_stop(slave_address);
    return count;
}